package com.example.activity.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.example.activity.constant.*;
import com.example.activity.dto.DeployInfoDTO;
import com.example.activity.exception.BizException;
import com.example.activity.mapper.*;
import com.example.activity.po.*;
import com.example.activity.service.*;
import com.example.activity.so.acvitivi.DeployInfoSO;
import com.example.activity.utils.FileUploadUtil;
import com.example.activity.utils.TimeUtil;
import com.example.activity.vo.acvitivi.DeployInfoVO;
import com.example.activity.vo.acvitivi.DeployedVO;
import com.example.activity.vo.upload.FileUpDowmInfoVO;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.*;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ProcessServiceImpl implements ProcessService {

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;

    @Resource
    private ProcessMapper processMapper;

    @Resource
    private FileUpLoadService fileUpLoadService;

    @Resource
    private DeployInfoService deployInfoService;

    @Resource
    private FileInfoRelService fileInfoRelService;

    @Resource
    private FileInfoService fileInfoService;

    @Resource
    private FileInfoMapper fileInfoMapper;

    @Resource
    private DeployInfoMapper deployInfoMapper;

    @Override
    public Deployment deployment() {
        // 流程定义key
        final String key = "sptest";


        Deployment result = repositoryService.createDeployment()
                .addClasspathResource("process/sptest.bpmn20.xml")
                .addClasspathResource("process/sptest.png")
                .name("审批测试")
                .deploy();


        return result;
    }

    /**
     * 查询部署列表
     *
     * @param deployInfoSO
     * @return
     */
    @Override
    public List<DeployedVO> deployedList(DeployInfoSO deployInfoSO) {
        return processMapper.deployedList(deployInfoSO);
    }

    /**
     * 启动流程实例
     *
     * @param account
     * @return
     */
    @Override
    public ProcessInstance startProcess(String account) {
        // 流程定义key
        final String key = "sptest";
        // 设置流程启动者
        Authentication.setAuthenticatedUserId(account);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key);

        return processInstance;
    }

    /**
     * 保存工作流配置信息
     *
     * @param deployInfoVO 文件信息
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public DeployInfoVO saveDeployInfo(DeployInfoVO deployInfoVO) {
        // 1.参数校验
        baseParamCheck(deployInfoVO);

        Long fileInfoId = deployInfoVO.getFileUpDowmInfoVO().getId();
        String flowKeyVO = deployInfoVO.getFlowKey();
        Long id = deployInfoVO.getId();

        DeployInfoPO findOne = deployInfoService.getById(id);

        DeployInfoPO deployInfoPO;
        if (findOne == null) {
            // 2.新增流程信息
            deployInfoPO = buildDeployInfoPO(deployInfoVO);
            deployInfoService.save(deployInfoPO);
        } else {
            // 3.更新流程信息

            // 3.1 校验流程key是否重复
            if (findOne.getFlowKey().equals(flowKeyVO)) {
                throw new BizException("流程定义key不能重复");
            }

            // 3.2 校验流程是否运行中
            Integer deployStatus = findOne.getDeployStatus();
            if (DeployStatusEnum.checkActRunning(deployStatus)) {
                throw new BizException(String.format("流程id:%s运行中，不能更新部署", findOne.getDeployId()));
            }

            deployInfoPO = buildDeployInfoPO(deployInfoVO);
            deployInfoService.updateById(deployInfoPO);
        }

        // 关联文件上传表
        fileInfoRelService.saveOrUpdateFileTableRel(FileModuleRelTableEnum.act_upload_rel, fileInfoId, String.valueOf(deployInfoPO.getId()));

        // 封装返回
        deployInfoVO.setId(deployInfoPO.getId());
        deployInfoVO.setCreateTime(deployInfoPO.getCreateTime());
        deployInfoVO.setUpdateTime(deployInfoPO.getUpdateTime());
        deployInfoVO.setFlowName(deployInfoPO.getFlowName());
        deployInfoVO.setFlowKey(deployInfoPO.getFlowKey());
        deployInfoVO.setDeployId(deployInfoPO.getDeployId());
        deployInfoVO.setDeployStatus(deployInfoPO.getDeployStatus());
        deployInfoVO.setDeployTime(deployInfoPO.getDeployTime());
        deployInfoVO.setRemark(deployInfoPO.getRemark());

        return deployInfoVO;
    }


    /**
     * 删除流程部署信息
     *
     * @param deployInfoIdList 主键集合
     * @return true删除成功，false删除失败
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean deleteDeploy(List<Long> deployInfoIdList) {
        // 校验
        if (CollectionUtil.isEmpty(deployInfoIdList)) {
            throw new BizException("请求参数为空");
        }

        List<DeployInfoPO> deployInfoPOList = deployInfoService.listByIds(deployInfoIdList);
        List<DeployInfoPO> runningList = deployInfoPOList.stream().filter(e -> DeployStatusEnum.checkActRunning(e.getDeployStatus())).collect(Collectors.toList());
        // 存在运行中的流程，不能删除
        if (CollectionUtil.isNotEmpty(runningList)) {
            log.error("流程运行中:【{}】", JSONUtil.toJsonStr(runningList));
            throw new BizException("流程运行中，不能删除");
        }

        // 删除部署
        List<String> deployIdList = deployInfoPOList.stream().map(DeployInfoPO::getDeployId).collect(Collectors.toList());
        for (String deployId : deployIdList) {
            // 工作流普通删除
            repositoryService.deleteDeployment(deployId);
        }
        deployInfoMapper.deleteDeploy(deployInfoIdList);

        return true;
    }

    private DeployInfoPO buildDeployInfoPO(DeployInfoVO deployInfoVO) {
        DeployInfoPO deployInfoPO = new DeployInfoPO();

        deployInfoPO.setId(deployInfoVO.getId());
        deployInfoPO.setFlowName(deployInfoVO.getFlowName());
        deployInfoPO.setFlowKey(deployInfoVO.getFlowKey());
        deployInfoPO.setRemark(deployInfoVO.getRemark());
        // 流程已上传
        deployInfoPO.setDeployStatus(DeployStatusEnum.UPLOAD.getCode());

        return deployInfoPO;
    }

    private void baseParamCheck(DeployInfoVO deployInfoVO) {
        String flowName = deployInfoVO.getFlowName();
        if (StrUtil.isBlank(flowName)) {
            throw new BizException("flowName为空");
        }

        String flowKeyVO = deployInfoVO.getFlowKey();
        if (StrUtil.isBlank(flowKeyVO)) {
            throw new BizException("flowKey为空");
        }

        // 文件信息校验
        FileUpDowmInfoVO fileUpDownInfoVO = deployInfoVO.getFileUpDowmInfoVO();
        if (fileUpDownInfoVO == null) {
            throw new BizException("文件信息为空");
        }
        if (fileUpDownInfoVO.getId() == null) {
            throw new BizException("文件关联id为空");
        }
    }

    /**
     * 部署流程
     *
     * @param deployInfoIdList DeployInfoVO
     * @return List<DeployInfoVO>
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<DeployInfoVO> deploy(List<Long> deployInfoIdList) {
        // 1.校验
        if (CollectionUtil.isEmpty(deployInfoIdList)) {
            throw new BizException("部署流程参数id为空");
        }
        List<DeployInfoPO> deployInfoPOList = deployInfoService.listByIds(deployInfoIdList);
        if (CollectionUtil.isEmpty(deployInfoPOList)) {
            throw new BizException("部署流程信息为空");
        }

        // 运行中的流程不能重新部署
        for (DeployInfoPO deployInfoPO : deployInfoPOList) {
            Integer deployStatus = deployInfoPO.getDeployStatus();
            if (DeployStatusEnum.checkActRunning(deployStatus)) {
                throw new BizException(String.format("流程id:%s运行中，不能重新部署", deployInfoPO.getDeployId()));
            }
        }


        // 2.下载流程文件
        List<FileInfoRelPO> fileRelPOList = fileInfoRelService.lambdaQuery().in(FileInfoRelPO::getTableIdStr, deployInfoIdList).list();
        if (CollectionUtil.isEmpty(fileRelPOList)) {
            throw new BizException("部署流程文件关联信息为空");
        }
        List<Long> fileIdList = fileRelPOList.stream().map(FileInfoRelPO::getFileInfoId).collect(Collectors.toList());
        List<FileInfoPO> fileInfoPOList = fileInfoService.listByIds(fileIdList);
        if (CollectionUtil.isEmpty(fileInfoPOList)) {
            throw new BizException("部署流程文件信息为空");
        }


        // 文件id-文件信息 map
        Map<Long, FileInfoPO> id_fileInfo_map = fileInfoPOList.stream().collect(Collectors.toMap(FileInfoPO::getId, o -> o));
        // 部署id-文件id map
        Map<String, Long> fileInfoId_tableIdStr_map = fileRelPOList.stream().collect(Collectors.toMap(FileInfoRelPO::getTableIdStr, FileInfoRelPO::getFileInfoId));
        // 查询封装文件信息
        FileUpDowmInfoVO fileVO = new FileUpDowmInfoVO();
        fileVO.setIdList(fileInfoPOList.stream().map(FileInfoPO::getId).collect(Collectors.toList()));
        List<FileUpDowmInfoVO> fileRelInfoList = fileInfoMapper.fileRelInfoList(fileVO);
        // 文件id-文件信息 map
        Map<Long, FileUpDowmInfoVO> id_fileRelInfo_map = fileRelInfoList.stream().collect(Collectors.toMap(FileUpDowmInfoVO::getId, o -> o));

        // 3.流程下载流程并部署
        List<DeployInfoVO> deployInfoVOList = new ArrayList<>();
        for (DeployInfoPO deployPO : deployInfoPOList) {
            Long deployInfoId = deployPO.getId();

            Long fileId = fileInfoId_tableIdStr_map.get(String.valueOf(deployInfoId));
            FileInfoPO fileInfoPO = id_fileInfo_map.get(fileId);

            // 文件路径
            String filePath = fileInfoPO.getFilePath();
            // 文件名称
            String fileName = fileInfoPO.getFileName();

            String fullFilePath = Paths.get(LocalConstant.BASE_ACTIVITI_UPLOAD, filePath, fileName).toAbsolutePath().toString();

            InputStream inputStream = null;
            Deployment deployment;
            try {
                // 下载文件
                inputStream = fileUpLoadService.downloadFileStream(fullFilePath);

                // 部署文件
                deployment = repositoryService.createDeployment()
                        .name(deployPO.getFlowName())
                        .addInputStream(fileName, inputStream)
                        .deploy();
            } finally {
                FileUploadUtil.closeStream(inputStream);
            }

            LocalDateTime deployTime = TimeUtil.dateToLocalDateTime(deployment.getDeploymentTime());

            // 4.更新部署信息
            DeployInfoDTO updateDeployInfo = new DeployInfoDTO();
            updateDeployInfo.setId(deployInfoId);
            updateDeployInfo.setDeployId(deployment.getId());
            updateDeployInfo.setDeployStatus(DeployStatusEnum.DEPLOY.getCode());
            updateDeployInfo.setDeployTime(deployTime);
            deployInfoMapper.updateDeployInfo(updateDeployInfo);

            // 5.封装返回值
            DeployInfoVO vo = new DeployInfoVO();
            vo.setId(deployInfoId);
            vo.setCreateTime(deployPO.getCreateTime());
            vo.setUpdateTime(deployPO.getUpdateTime());

            vo.setFlowName(deployPO.getFlowName());
            vo.setFlowKey(deployPO.getFlowKey());
            vo.setDeployId(deployment.getId());
            vo.setDeployStatus(DeployStatusEnum.DEPLOY.getCode());
            vo.setDeployTime(deployTime);
            vo.setRemark(deployPO.getRemark());

            FileUpDowmInfoVO fileUpDowmInfoVO = id_fileRelInfo_map.get(fileId);
            vo.setFileUpDowmInfoVO(fileUpDowmInfoVO);

            deployInfoVOList.add(vo);
        }

        return deployInfoVOList;
    }


    /**
     * 下载部署文件流
     *
     * @param fileId   文件id
     * @param response 响应流
     */
    public void downloadDeployFileStream(Long fileId, HttpServletResponse response) {
        // 1.校验
        if (fileId == null) {
            throw new BizException("部署流程文件id为空");
        }

        // 2.查询文件信息
        FileInfoPO fileInfoPO = fileInfoService.getById(fileId);
        if (fileInfoPO == null) {
            throw new BizException("部署流程文件信息为空");
        }

        // 文件路径
        String filePath = fileInfoPO.getFilePath();
        // 文件名称
        String fileName = fileInfoPO.getFileName();
        String fullFilePath = Paths.get(LocalConstant.BASE_ACTIVITI_UPLOAD, filePath, fileName).toAbsolutePath().toString();

        // 3.下载文件
        InputStream inputStream = fileUpLoadService.downloadFileStream(fullFilePath);
        // 4.返回文件流
        try {
            fileUpLoadService.downStream(fileName, "inline", inputStream, response);
        } catch (IOException e) {
            log.error("下载文件:{}失败:", fileName, e);
            throw new BizException("下载文件失败");
        }

    }

}
